#include "os_util.hh"
#include "string_util.hh"

#if defined(_WIN32) || defined(WIN32) || defined(_WIN64)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN // avoid including unnecessary WIN32 header files
#endif
#include <windows.h>
#pragma comment(lib, "shlwapi.lib")
#pragma comment(lib, "IPHLPAPI.lib")
#pragma comment(lib, "Version.lib")
#ifndef DISABLE_TRACE
#include "detail/stack_trace_windows.hh"
#endif // DISABLE_TRACE
#include <WinSock2.h>
#include <direct.h>
#include <iphlpapi.h>
#include <shlwapi.h>
#include <ws2tcpip.h>
#define DELIMETER "\\"
#else
#include "detail/stack_trace_linux.hh"
#include <arpa/inet.h>
#include <dirent.h>
#include <errno.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define DELIMETER "/"
#endif // _WIN32

#include <array>
#include <climits>
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <memory>
#include <random>
#include <stdexcept>
#include <unordered_map>

namespace kratos {
namespace util {

#ifndef LUA_PBC

std::string get_host_ip(const std::string &host) {
  if (host.empty()) {
    return "";
  }
  if (host == "localhost") {
    return "127.0.0.1";
  }
  bool isIp = is_ip_address(host);
  if (isIp) {
    return host;
  }
  auto ent = gethostbyname(host.c_str());
  if (!ent) {
    return host;
  }
  auto ip = inet_ntoa(*((struct in_addr *)ent->h_addr));
  if (!ip) {
    return "";
  }
  return ip;
}

#endif // LUA_PBC

std::string get_binary_path() {
  const static int SIZE = 512;
  char path[SIZE] = {0};
#if defined(_WIN32) || defined(WIN32) || defined(_WIN64)
  ::GetModuleFileNameA(NULL, path, sizeof(path));
#else
  int result = readlink("/proc/self/exe", path, sizeof(path));
  if (result < 0 || (result >= SIZE - 1)) {
    return "";
  }
  path[result] = '\0';
  for (int i = result; i >= 0; i--) {
    if (path[i] == '/') {
      path[i] = '\0';
      break;
    }
  }
  return path;
#endif // _WIN32
  std::string temp(path);
  size_t index = temp.rfind("\\");
  std::string execPath;
  execPath.append(temp, 0, index);
  return execPath;
}

std::string get_binary_name() {
  const static int SIZE = 512;
  char path[SIZE] = {0};
#if defined(_WIN32) || defined(WIN32) || defined(_WIN64)
  ::GetModuleFileNameA(NULL, path, sizeof(path));
#else
  int result = readlink("/proc/self/exe", path, sizeof(path));
  if (result < 0 || (result >= SIZE - 1)) {
    return "";
  }
  path[result] = '\0';
#endif // _WIN32
  return util::get_file_name(path);
}

bool get_file_in_directory(const std::string &directory,
                           const std::string suffix,
                           std::vector<std::string> &fileNames) {
  namespace fs = std::filesystem;
  if (!is_path_exists(directory)) {
    return false;
  }
#if defined(_WIN32) || defined(WIN32) || defined(_WIN64)
  WIN32_FIND_DATAA findData;
  HANDLE handle = INVALID_HANDLE_VALUE;
  std::string path = directory + "\\*.*";
  handle = ::FindFirstFileA(path.c_str(), &findData);
  if (handle == INVALID_HANDLE_VALUE) {
    return false;
  }
  for (;;) {
    if (strcmp(findData.cFileName, ".") == 0 ||
        strcmp(findData.cFileName, "..") == 0) {
      if (!::FindNextFileA(handle, &findData)) {
        break;
      }
      continue;
    }
    std::string fullName =
        (fs::path(directory) / fs::path(findData.cFileName)).string();
    if (suffix.empty()) {
      fileNames.push_back(fullName);
    } else {
      if (endWith(fullName, suffix)) {
        fileNames.push_back(fullName);
      }
    }
    if (!::FindNextFileA(handle, &findData)) {
      break;
    }
  }
  ::FindClose(handle);
#else
  DIR *dir = 0;
  struct dirent *ent = 0;
  dir = opendir(directory.c_str());
  if (!dir) {
    return false;
  }
  while (0 != (ent = readdir(dir))) {
    if (!(ent->d_type & DT_DIR)) {
      if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) {
        continue;
      }
      std::string fullName =
          (fs::path(directory) / fs::path(ent->d_name)).string();
      if (suffix.empty()) {
        fileNames.push_back(fullName);
      } else {
        if (endWith(fullName, suffix)) {
          fileNames.push_back(fullName);
        }
      }
    }
  }
  closedir(dir);
#endif // _WIN32
  return true;
}

bool get_file_name_in_directory(const std::string &directory,
                                const std::string suffix,
                                std::vector<std::string> &fileNames) {
  if (!is_path_exists(directory)) {
    return false;
  }
#if defined(_WIN32) || defined(WIN32) || defined(_WIN64)
  WIN32_FIND_DATAA findData;
  HANDLE handle = INVALID_HANDLE_VALUE;
  std::string path = directory + "\\*.*";
  handle = ::FindFirstFileA(path.c_str(), &findData);
  if (handle == INVALID_HANDLE_VALUE) {
    return false;
  }
  for (;;) {
    if (strcmp(findData.cFileName, ".") == 0 ||
        strcmp(findData.cFileName, "..") == 0) {
      if (!::FindNextFileA(handle, &findData)) {
        break;
      }
      continue;
    }
    std::string fullName = directory + "\\" + findData.cFileName;
    if (suffix.empty()) {
      fileNames.push_back(findData.cFileName);
    } else {
      if (endWith(fullName, suffix)) {
        fileNames.push_back(findData.cFileName);
      }
    }
    if (!::FindNextFileA(handle, &findData)) {
      break;
    }
  }
  ::FindClose(handle);
#else
  DIR *dir = 0;
  struct dirent *ent = 0;
  dir = opendir(directory.c_str());
  if (!dir) {
    return false;
  }
  while (0 != (ent = readdir(dir))) {
    if (!(ent->d_type & DT_DIR)) {
      if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) {
        continue;
      }
      std::string fullName = directory + "/" + ent->d_name;
      if (suffix.empty()) {
        fileNames.push_back(ent->d_name);
      } else {
        if (endWith(fullName, suffix)) {
          fileNames.push_back(ent->d_name);
        }
      }
    }
  }
  closedir(dir);
#endif // _WIN32
  return true;
}

bool get_file_in_directory_recursive(const std::string &directory,
                                     const std::string suffix,
                                     std::vector<std::string> &fileNames) {
  namespace fs = std::filesystem;
  if (!fs::exists(directory)) {
    return false;
  }
  auto begin = fs::recursive_directory_iterator(directory);
  auto end = fs::recursive_directory_iterator();
  for (auto it = begin; it != end; it++) {
    const auto &entry = *it;
    if (fs::is_regular_file(entry) && endWith(entry.path().string(), suffix)) {
      fileNames.emplace_back(entry.path().string());
    }
  }
  return true;
}

bool is_path_exists(const std::string &path) {
#if defined(_WIN32) || defined(WIN32) || defined(_WIN64)
  return (TRUE == ::PathFileExistsA(path.c_str()));
#else
  return !access(path.c_str(), F_OK);
#endif // _WIN32
}

bool remove_file(const std::string &filePath) {
  return (::remove(filePath.c_str()) == 0);
}

std::string complete_path(const std::string &base, const std::string &sub) {
  if (endWith(base, DELIMETER)) {
    return base + sub;
  } else {
    return base + DELIMETER + sub;
  }
}

std::string complete_path_url(const std::string &base, const std::string &sub) {
  if (endWith(base, "/")) {
    return base + sub;
  } else {
    return base + "/" + sub;
  }
}

bool rename_file(const std::string &srcFileName,
                 const std::string &newFileName) {
  return (rename(srcFileName.c_str(), newFileName.c_str()) == 0);
}

std::string get_file_ext(const std::string &fileName) {
  auto pos = fileName.find_last_of('.');
  return std::string(fileName.begin() + pos + 1, fileName.end());
}

std::uint32_t get_random_uint32(std::uint32_t a, std::uint32_t b) {
  std::random_device rd;
  std::mt19937 mt(rd());
  std::uniform_int_distribution<std::uint32_t> dist(a, b);
  return dist(mt);
}

auto _make_dir(const std::string &path) -> bool {
#if defined(_WIN32) || defined(WIN32) || defined(_WIN64)
  return (::_mkdir(path.c_str()) == 0);
#else
  return (::mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0);
#endif
}

auto make_dir(const std::string &path) -> bool {
  std::vector<std::string> result;
  util::split(path, DELIMETER, result);
  if (result.empty()) {
    return false;
  }
  std::string full_path;
  for (const auto &name : result) {
    if (full_path.empty()) {
      full_path = name;
    } else {
      full_path = complete_path(full_path, name);
    }
    if (!is_path_exists(full_path)) {
      if (!_make_dir(full_path)) {
        return false;
      }
    }
  }
  return true;
}

auto rm_empty_dir(const std::string &path) -> bool {
#if defined(_WIN32) || defined(WIN32) || defined(_WIN64)
  return (::_rmdir(path.c_str()) == 0);
#else
  return (::rmdir(path.c_str()) == 0);
#endif
}

auto get_pid() -> int {
#ifndef _WIN32
  return getpid();
#else
  return (int)::GetCurrentProcessId();
#endif // _WIN32
}

auto get_current_stack_trace_info(int depth) -> std::string {
#ifndef DISABLE_TRACE
  return util::StackTrace::get_stack_frame_info(depth);
#else
  return "";
#endif // DISABLE_TRACE
}

auto get_last_modify_time(const std::string &path) -> std::time_t {
#ifndef _WIN32
  struct stat st;
  memset(&st, 0, sizeof(st));
  if (lstat(path.c_str(), &st) < 0) {
    return 0;
  }
  return st.st_mtime;
#else
  struct _stat st;
  int result = _stat(path.c_str(), &st);
  if (result) {
    return 0;
  }
  return st.st_mtime;
#endif // _WIN32
}

auto get_file_content(const std::string &file_path, int line, int related_line,
                      std::string &content) -> bool {
  if (line <= 0 || related_line < 0) {
    return false;
  }
  content.clear();
  std::ifstream ifs;
  ifs.open(file_path);
  if (!ifs) {
    return false;
  }
  auto begin_line = line - related_line > 0 ? line - related_line : 0;
  int cur_line = 0;
  std::string line_str;
  while (std::getline(ifs, line_str)) {
    cur_line += 1;
    if (cur_line > line + related_line) {
      break;
    }
    if (cur_line >= begin_line) {
      if (cur_line == line) {
        content += "* " + std::to_string(cur_line) + "  " + line_str + "\n";
      } else {
        content += "  " + std::to_string(cur_line) + "  " + line_str + "\n";
      }
    }
  }
  return true;
}

#if defined(_WIN32) || defined(WIN32) || defined(_WIN64)
using FileHandle = HANDLE;
#else
using FileHandle = int;
#endif
using FileHandleMap = std::unordered_map<std::string, FileHandle>;
static FileHandleMap handle_map;

auto try_lock_file(const std::string &file_path) -> bool {
#if defined(_WIN32) || defined(WIN32) || defined(_WIN64)
  auto lock_file = ::CreateFileA(file_path.c_str(),     // open
                                 FILE_APPEND_DATA,      // open for writing
                                 0,                     // allow one reader
                                 NULL,                  // no security
                                 OPEN_ALWAYS,           // open or create
                                 FILE_ATTRIBUTE_NORMAL, // normal file
                                 NULL);                 // no attr. template
  if (lock_file == INVALID_HANDLE_VALUE) {
    return false;
  } else {
    handle_map.emplace(file_path, lock_file);
    return true;
  }
#else
  auto lock_file = open(file_path.c_str(), O_CREAT | O_RDWR, 0666);
  int rc = flock(lock_file, LOCK_EX | LOCK_NB);
  if (rc && (EWOULDBLOCK == errno)) {
    return false;
  } else {
    handle_map.emplace(file_path, lock_file);
    return true;
  }
#endif
}

auto try_unlock_file(const std::string &file_path, bool is_remove) -> bool {
  auto it = handle_map.find(file_path);
  if (it == handle_map.end()) {
    return false;
  }
#if defined(_WIN32) || defined(WIN32) || defined(_WIN64)
  ::CloseHandle(it->second);
#else
  close(it->second);
#endif
  handle_map.erase(it);
  if (is_remove) {
    util::remove_file(file_path);
  }
  return true;
}

auto unlock_all_file(bool is_remove) -> void {
  for (const auto &[file_path, h] : handle_map) {
#if defined(_WIN32) || defined(WIN32) || defined(_WIN64)
    ::CloseHandle(h);
#else
    close(h);
#endif
    if (is_remove) {
      util::remove_file(file_path);
    }
  }
  handle_map.clear();
}

#if defined(_WIN32) || defined(_WIN64)
#ifndef LUA_PBC
auto get_mac_addr_list(std::vector<std::string> &mac_addr_list) -> void {
  ULONG buf_len{0};
  ::GetAdaptersAddresses(AF_UNSPEC, 0, nullptr, nullptr, &buf_len);
  std::unique_ptr<char> buf_ptr(new char[buf_len]);
  auto *cur_addr = (PIP_ADAPTER_ADDRESSES)buf_ptr.get();
  auto retval =
      ::GetAdaptersAddresses(AF_UNSPEC, 0, nullptr, cur_addr, &buf_len);
  if (retval != NO_ERROR) {
    return;
  }
  for (PIP_ADAPTER_ADDRESSES addr = cur_addr; addr != nullptr;
       addr = addr->Next) {
    if (addr->PhysicalAddressLength == 6) {
      std::string mac_addr;
      mac_addr += std::to_string(addr->PhysicalAddress[0]);
      mac_addr += "-";
      mac_addr += std::to_string(addr->PhysicalAddress[1]);
      mac_addr += "-";
      mac_addr += std::to_string(addr->PhysicalAddress[2]);
      mac_addr += "-";
      mac_addr += std::to_string(addr->PhysicalAddress[3]);
      mac_addr += "-";
      mac_addr += std::to_string(addr->PhysicalAddress[4]);
      mac_addr += "-";
      mac_addr += std::to_string(addr->PhysicalAddress[5]);
      mac_addr_list.emplace_back(std::move(mac_addr));
    }
  }
}
auto get_ip_addr_list(std::vector<std::string> &ip_addr_list) -> void {
  ULONG buf_len{0};
  ::GetAdaptersAddresses(AF_UNSPEC,
                         GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST |
                             GAA_FLAG_SKIP_DNS_SERVER |
                             GAA_FLAG_SKIP_FRIENDLY_NAME,
                         nullptr, nullptr, &buf_len);
  std::unique_ptr<char> buf_ptr(new char[buf_len]);
  auto *cur_addr = (PIP_ADAPTER_ADDRESSES)buf_ptr.get();
  auto retval = ::GetAdaptersAddresses(
      AF_UNSPEC,
      GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST |
          GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME,
      nullptr, cur_addr, &buf_len);
  if (retval != NO_ERROR) {
    return;
  }
  for (PIP_ADAPTER_ADDRESSES addr = cur_addr; addr != nullptr;
       addr = addr->Next) {
    if (IF_TYPE_SOFTWARE_LOOPBACK == addr->IfType) {
      continue;
    }
    for (PIP_ADAPTER_UNICAST_ADDRESS uaddr = addr->FirstUnicastAddress;
         uaddr != nullptr; uaddr = uaddr->Next) {
      if (AF_INET == uaddr->Address.lpSockaddr->sa_family) {
        auto *ipv4 = reinterpret_cast<SOCKADDR_IN *>(uaddr->Address.lpSockaddr);
        char buf[64] = {0};
        ::inet_ntop(AF_INET, &(ipv4->sin_addr), buf, sizeof(buf));
        ip_addr_list.push_back(buf);
      }
    }
  }
}
#endif // LUA_PBC
#else
auto get_mac_addr_list(std::vector<std::string> &mac_addr_list) -> void {
  struct ifconf ifc;
  memset(&ifc, 0, sizeof(ifc));
  auto sock = socket(AF_INET, SOCK_DGRAM, 0);
  struct ifreq ifr[10];
  ifc.ifc_len = sizeof(ifr);
  ifc.ifc_buf = (char *)ifr;
  ioctl(sock, SIOCGIFCONF, (char *)&ifc);
  auto if_count = ifc.ifc_len / (sizeof(ifreq));
  for (decltype(if_count) i = 0; i < if_count; i++) {
    if (ioctl(sock, SIOCGIFHWADDR, &ifr[i]) == 0) {
      if (ifr[i].ifr_flags & IFF_RUNNING) {
        char buf[32] = {0};
        memcpy(buf, ifr[i].ifr_hwaddr.sa_data, 6);
        std::string mac_addr;
        mac_addr += std::to_string(buf[0]);
        mac_addr += "-";
        mac_addr += std::to_string(buf[1]);
        mac_addr += "-";
        mac_addr += std::to_string(buf[2]);
        mac_addr += "-";
        mac_addr += std::to_string(buf[3]);
        mac_addr += "-";
        mac_addr += std::to_string(buf[4]);
        mac_addr += "-";
        mac_addr += std::to_string(buf[5]);
        mac_addr_list.emplace_back(std::move(mac_addr));
      }
    }
  }
}
auto get_ip_addr_list(std::vector<std::string> &ip_addr_list) -> void {
  struct ifaddrs *if_addrs_ptr{nullptr};
  getifaddrs(&if_addrs_ptr);
  while (if_addrs_ptr) {
    if (if_addrs_ptr->ifa_addr->sa_family == AF_INET) {
      auto *tmp_ptr = &((struct sockaddr_in *)if_addrs_ptr->ifa_addr)->sin_addr;
      char buf[64] = {0};
      inet_ntop(AF_INET, tmp_ptr, buf, sizeof(buf));
      ip_addr_list.push_back(buf);
    }
    if_addrs_ptr = if_addrs_ptr->ifa_next;
  }
}
#endif // defined(_WIN32) || defined(_WIN64)

auto get_mac_hash(const std::string &mac_addr) -> std::uint64_t {
  std::vector<std::string> result;
  util::split(mac_addr, "-", result);
  std::uint64_t mac_hash{0};
  for (std::size_t i = 0; i < result.size(); i++) {
    mac_hash |= std::uint64_t(std::stoul(result[i])) << (i * 8);
  }
  return mac_hash;
}

auto get_ip_hash(const std::string &ip_addr) -> std::uint32_t {
  std::vector<std::string> result;
  util::split(ip_addr, ".", result);
  std::uint32_t ip_hash{0};
  for (std::size_t i = 0; i < result.size(); i++) {
    ip_hash |= std::uint32_t(std::stoul(result[i])) << (i * 8);
  }
  return ip_hash;
}

auto set_current_object_search_path(const std::string &path) -> void {
#if defined(_WIN32) || defined(_WIN64) || defined(WIN32)
  ::SetDllDirectoryA(path.c_str());
#else
  auto *cur_path = getenv("LD_LIBRARY_PATH");
  std::string cur_env(cur_path ? cur_path : "");
  cur_env += ";" + path;
  setenv("LD_LIBRARY_PATH", cur_env.c_str(), 1);
#endif // defined(_WIN32) || defined(_WIN64) || defined(WIN32)
}

auto get_network_type(const std::string &host) -> std::string {
  std::string src;
  if (has_sub_string(host, ":\\\\")) {
    return src.assign(host, 0, host.find(":\\\\"));
  } else {
    return "tcp";
  }
}

auto get_sys_error_str() -> std::string {
#if defined(_WIN32) || defined(_WIN64) || defined(WIN32)
  auto error_id = ::GetLastError();
  if (error_id == 0) {
    return "No error";
  }
  LPSTR buffer = nullptr;
  size_t size = ::FormatMessageA(
      FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
          FORMAT_MESSAGE_IGNORE_INSERTS,
      NULL, error_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&buffer,
      0, NULL);
  std::string error_message(buffer, size);
  ::LocalFree(buffer);
  return error_message;
#else
  auto *err_str = strerror(errno);
  return (err_str ? err_str : "");
#endif
}

auto is_debug_module(const std::string &mod_path) -> bool {
#if defined(_WIN32) || defined(_WIN64) || defined(WIN32)
  std::string win_path{mod_path};
  std::replace(win_path.begin(), win_path.end(), '/', '\\');
  VS_FIXEDFILEINFO *pFileInfo{nullptr};
  UINT puLenFileInfo{0};
  // Get the version information for the file requested
  auto dwSize = GetFileVersionInfoSize(win_path.c_str(), nullptr);
  if (dwSize == 0) {
    return true;
  }
  auto pbVersionInfo = std::make_unique<char[]>(dwSize);
  if (!GetFileVersionInfo(win_path.c_str(), 0, dwSize, pbVersionInfo.get())) {
    return true;
  }
  if (!VerQueryValue(pbVersionInfo.get(), TEXT("\\"), (LPVOID *)&pFileInfo,
                     &puLenFileInfo)) {
    return true;
  }
  return (pFileInfo->dwFileFlags & VS_FF_DEBUG);
#else
#if defined(DEBUG) || defined(_DEBUG)
  return true;
#else
  return false;
#endif // defined(DEBUG) || defined(_DEBUG)
#endif // defined(_WIN32) || defined(_WIN64) || defined(WIN32)
}

auto get_last_errno_message() -> std::string {
#if defined(_WIN32) || defined(_WIN64) || defined(WIN32)
  auto err_code = ::GetLastError();
  if (err_code) {
    LPVOID msg_buf_ptr{nullptr};
    auto buf_len = ::FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
            FORMAT_MESSAGE_IGNORE_INSERTS,
        nullptr, err_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR)&msg_buf_ptr, 0, nullptr);
    if (buf_len) {
      LPCSTR lpMsgStr = (LPCSTR)msg_buf_ptr;
      std::string result(lpMsgStr, lpMsgStr + buf_len);
      ::LocalFree(msg_buf_ptr);
      return result;
    }
  }
  return std::string();
#else
  if (errno) {
    std::size_t msg_len = 256;
    auto msg_ptr = std::make_unique<char[]>(msg_len);
    auto *error_str = strerror_r(errno, msg_ptr.get(), msg_len);
    if (error_str) {
      return error_str;
    }
  }
  return std::string();
#endif // defined(_WIN32) || defined(_WIN64) || defined(WIN32)
}

auto set_env(const char *name, const char *value) -> void {
#if defined(_WIN32) || defined(_WIN64) || defined(WIN32)
  _putenv_s(name, value);
#else
  setenv(name, value, 1);
#endif // defined(_WIN32) || defined(_WIN64) || defined(WIN32)
}

auto is_port_is_use(int port) -> bool {
  auto socket_fd = socket(AF_INET, SOCK_STREAM, 0);
  struct sockaddr_in sa;
  memset(&sa, 0, sizeof(sa));
  sa.sin_family = AF_INET;
  sa.sin_port = htons((unsigned short)port);
#if (defined(WIN32) || defined(_WIN32) || defined(_WIN64))
  sa.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
#else
  sa.sin_addr.s_addr = htonl(INADDR_ANY);
#endif // (defined(WIN32) || defined(_WIN32) || defined(_WIN64))
  auto error = bind(socket_fd, (struct sockaddr *)&sa, sizeof(struct sockaddr));
#if (defined(WIN32) || defined(_WIN32) || defined(_WIN64))
  closesocket(socket_fd);
  if (error == WSAEADDRINUSE) {
    return true;
  }
#else
  if (error && errno == EADDRINUSE) {
    close(socket_fd);
    return true;
  }
  close(socket_fd);
#endif // #if (defined(WIN32) || defined(_WIN32) || defined(_WIN64))
  return false;
}

auto get_any_port(int start, int end) -> int {
  auto range = start < end ? end - start : start - end;
  for (auto i = 0; i < range; i++) {
    auto port =
        (int)get_random_uint32(std::uint32_t(start), std::uint32_t(end));
    if (!is_port_is_use(port)) {
      return port;
    }
  }
  return 0;
}

auto create_uuid() -> std::string {
  static std::string mac_addr;
  static std::uint32_t id{1};
  if (mac_addr.empty()) {
    std::vector<std::string> mac_list;
    get_mac_addr_list(mac_list);
    if (mac_list.empty()) {
      return "";
    }
    mac_addr = mac_list[0];
  }
  if (mac_addr.empty()) {
    return "";
  }
  std::string uuid = mac_addr;
  uuid += "-" + std::to_string(get_random_uint32(1, 10000));
  uuid += "-" + std::to_string(get_pid());
  uuid += "-" + std::to_string(id++);
  return uuid;
}

#if (defined(WIN32) || defined(_WIN32) || defined(_WIN64))
#define popen_fn _popen
#define pclose_fn _pclose
#else
#define popen_fn popen
#define pclose_fn pclose
#endif

auto execute(const std::string &cmd_line) -> std::string {
  constexpr int BUF_LEN = 1024;
  auto buf_ptr = std::make_unique<char[]>(BUF_LEN);
  std::unique_ptr<std::FILE, decltype(&pclose_fn)> pipe(
      popen_fn(cmd_line.c_str(), "r"), pclose_fn);
  std::string result;
  if (!pipe) {
    throw std::invalid_argument("Invoking execute failed");
  }
  while (fgets(buf_ptr.get(), BUF_LEN, pipe.get())) {
    result += buf_ptr.get();
  }
  return result;
}

auto get_env(const std::string &key) -> std::string {
  auto *val = std::getenv(key.c_str());
  if (!val) {
    return "";
  }
  return val;
}

} // namespace util
} // namespace kratos
